<?php
 /*
     pCache - speed up the rendering by caching up the pictures

     Version     : 2.1.4
     Made by     : Jean-Damien POGOLOTTI
     Last Update : 19/01/2014

     This file can be distributed under the license you can find at :

                       http://www.pchart.net/license

     You can find the whole class documentation on the pChart web site.
 */

 /* pData class definition */
 class pCache
 {
     public $CacheFolder;
     public $CacheIndex;
     public $CacheDB;

     /* Class creator */
     public function __construct($Settings = "")
     {
     $CacheFolder	= isset($Settings["CacheFolder"]) ? $Settings["CacheFolder"] : "cache";
     $CacheIndex	= isset($Settings["CacheIndex"]) ? $Settings["CacheIndex"] : "index.db";
     $CacheDB		= isset($Settings["CacheDB"]) ? $Settings["CacheDB"] : "cache.db";

         $this->CacheFolder	= $CacheFolder;
         $this->CacheIndex	= $CacheIndex;
         $this->CacheDB	= $CacheDB;

         if (!file_exists($this->CacheFolder."/".$this->CacheIndex)) {
             touch($this->CacheFolder."/".$this->CacheIndex);
         }
         if (!file_exists($this->CacheFolder."/".$this->CacheDB)) {
             touch($this->CacheFolder."/".$this->CacheDB);
         }
     }

     /* Flush the cache contents */
     public function flush()
     {
         if (file_exists($this->CacheFolder."/".$this->CacheIndex)) {
             unlink($this->CacheFolder."/".$this->CacheIndex);
             touch($this->CacheFolder."/".$this->CacheIndex);
         }
         if (file_exists($this->CacheFolder."/".$this->CacheDB)) {
             unlink($this->CacheFolder."/".$this->CacheDB);
             touch($this->CacheFolder."/".$this->CacheDB);
         }
     }

     /* Return the MD5 of the data array to clearly identify the chart */
     public function getHash($Data, $Marker="")
     {
         return(md5($Marker.serialize($Data->Data)));
     }

     /* Write the generated picture to the cache */
     public function writeToCache($ID, $pChartObject)
     {
         /* Compute the paths */
         $TemporaryFile = $this->CacheFolder."/tmp_".mt_rand(0, 1000).".png";
         $Database      = $this->CacheFolder."/".$this->CacheDB;
         $Index         = $this->CacheFolder."/".$this->CacheIndex;

         /* Flush the picture to a temporary file */
         imagepng($pChartObject->Picture, $TemporaryFile);

         /* Retrieve the files size */
         $PictureSize = filesize($TemporaryFile);
         $DBSize      = filesize($Database);

         /* Save the index */
         $Handle = fopen($Index, 'ab');
         fwrite($Handle, $ID.",".$DBSize.",".$PictureSize.",".time().",0      \r\n");
         fclose($Handle);

         /* Get the picture raw contents */
         $Handle = fopen($TemporaryFile, 'rb');
         $Raw    = fread($Handle, $PictureSize);
         fclose($Handle);

         /* Save the picture in the solid database file */
         $Handle = fopen($Database, 'ab');
         fwrite($Handle, $Raw);
         fclose($Handle);

         /* Remove temporary file */
         unlink($TemporaryFile);
     }

     /* Remove object older than the specified TS */
     public function removeOlderThan($Expiry)
     {
         $this->dbRemoval(array("Expiry"=>$Expiry));
     }

     /* Remove an object from the cache */
     public function remove($ID)
     {
         $this->dbRemoval(array("Name"=>$ID));
     }

     /* Remove with specified criterias */
     public function dbRemoval($Settings)
     {
         $ID     = isset($Settings["Name"]) ? $Settings["Name"] : null;
         $Expiry = isset($Settings["Expiry"]) ? $Settings["Expiry"] : -(24*60*60);
         $TS     = time()-$Expiry;

         /* Compute the paths */
         $Database     = $this->CacheFolder."/".$this->CacheDB;
         $Index        = $this->CacheFolder."/".$this->CacheIndex;
         $DatabaseTemp = $this->CacheFolder."/".$this->CacheDB.".tmp";
         $IndexTemp    = $this->CacheFolder."/".$this->CacheIndex.".tmp";

         /* Single file removal */
         if ($ID != null) {
             /* Retrieve object informations */
             $Object = $this->isInCache($ID, true);

             /* If it's not in the cache DB, go away */
             if (!$Object) {
                 return(0);
             }
         }

         /* Create the temporary files */
         if (!file_exists($DatabaseTemp)) {
             touch($DatabaseTemp);
         }
         if (!file_exists($IndexTemp)) {
             touch($IndexTemp);
         }

         /* Open the file handles */
         $IndexHandle     = @fopen($Index, 'rb');
         $IndexTempHandle = @fopen($IndexTemp, 'wb');
         $DBHandle        = @fopen($Database, 'rb');
         $DBTempHandle    = @fopen($DatabaseTemp, 'wb');

         /* Remove the selected ID from the database */
         while (!feof($IndexHandle)) {
             $Entry    = fgets($IndexHandle, 4096);
             $Entry    = str_replace("\r", "", $Entry);
             $Entry    = str_replace("\n", "", $Entry);
             $Settings = preg_split("/,/", $Entry);

             if ($Entry != "") {
                 $PicID       = $Settings[0];
                 $DBPos       = $Settings[1];
                 $PicSize     = $Settings[2];
                 $GeneratedTS = $Settings[3];
                 $Hits        = $Settings[4];

                 if ($Settings[0] != $ID && $GeneratedTS > $TS) {
                     $CurrentPos  = ftell($DBTempHandle);
                     fwrite($IndexTempHandle, $PicID.",".$CurrentPos.",".$PicSize.",".$GeneratedTS.",".$Hits."\r\n");

                     fseek($DBHandle, $DBPos);
                     $Picture = fread($DBHandle, $PicSize);
                     fwrite($DBTempHandle, $Picture);
                 }
             }
         }

         /* Close the handles */
         fclose($IndexHandle);
         fclose($IndexTempHandle);
         fclose($DBHandle);
         fclose($DBTempHandle);

         /* Remove the prod files */
         unlink($Database);
         unlink($Index);

         /* Swap the temp & prod DB */
         rename($DatabaseTemp, $Database);
         rename($IndexTemp, $Index);
     }

     public function isInCache($ID, $Verbose=false, $UpdateHitsCount=false)
     {
         /* Compute the paths */
         $Index = $this->CacheFolder."/".$this->CacheIndex;

         /* Search the picture in the index file */
         $Handle = @fopen($Index, 'rb');
         while (!feof($Handle)) {
             $IndexPos = ftell($Handle);
             $Entry = fgets($Handle, 4096);
             if ($Entry != "") {
                 $Settings = preg_split("/,/", $Entry);
                 $PicID    = $Settings[0];
                 if ($PicID == $ID) {
                     fclose($Handle);

                     $DBPos       = $Settings[1];
                     $PicSize     = $Settings[2];
                     $GeneratedTS = $Settings[3];
                     $Hits        = (int)$Settings[4];

                     if ($UpdateHitsCount) {
                         $Hits++;
                         if (strlen($Hits) < 7) {
                             $Hits = $Hits.str_repeat(" ", 7-strlen($Hits));
                         }

                         $Handle = @fopen($Index, 'rb+');
                         fseek($Handle, $IndexPos);
                         fwrite($Handle, $PicID.",".$DBPos.",".$PicSize.",".$GeneratedTS.",".$Hits."\r\n");
                         fclose($Handle);
                     }

                     if ($Verbose) {
                         return(array("DBPos"=>$DBPos,"PicSize"=>$PicSize,"GeneratedTS"=>$GeneratedTS,"Hits"=>$Hits));
                     }
                     return(true);
                 }
             }
         }
         fclose($Handle);

         /* Picture isn't in the cache */
         return(false);
     }

     /* Automatic output method based on the calling interface */
     public function autoOutput($ID, $Destination="output.png")
     {
         if (php_sapi_name() == "cli") {
             $this->saveFromCache($ID, $Destination);
         } else {
             $this->strokeFromCache($ID);
         }
     }

     public function strokeFromCache($ID)
     {
         /* Get the raw picture from the cache */
         $Picture = $this->getFromCache($ID);

         /* Do we have a hit? */
         if ($Picture == null) {
             return(false);
         }

         header('Content-type: image/png');
         echo $Picture;

         return(true);
     }

     public function saveFromCache($ID, $Destination)
     {
         /* Get the raw picture from the cache */
         $Picture = $this->getFromCache($ID);

         /* Do we have a hit? */
         if ($Picture == null) {
             return(false);
         }

         /* Flush the picture to a file */
         $Handle = fopen($Destination, 'wb');
         fwrite($Handle, $Picture);
         fclose($Handle);

         /* All went fine */
         return(true);
     }

     public function getFromCache($ID)
     {
         /* Compute the path */
         $Database = $this->CacheFolder."/".$this->CacheDB;

         /* Lookup for the picture in the cache */
         $CacheInfo = $this->isInCache($ID, true, true);

         /* Not in the cache */
         if (!$CacheInfo) {
             return(null);
         }

         /* Get the database extended information */
         $DBPos   = $CacheInfo["DBPos"];
         $PicSize = $CacheInfo["PicSize"];

         /* Extract the picture from the solid cache file */
         $Handle = @fopen($Database, 'rb');
         fseek($Handle, $DBPos);
         $Picture = fread($Handle, $PicSize);
         fclose($Handle);

         /* Return back the raw picture data */
         return($Picture);
     }
 }
